package log

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"gitee.com/dsler-go/utils/model/log"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"strings"
	"time"
)

type ContextTraceKeyType string

var ContextTraceIDKey ContextTraceKeyType = "CONTEXT_TRACE_ID"

/*
# -1=debug,0=info(default),1=warn,2=error,3=fatal
Level               = 0
Dir                 = /data/logs/inott/epg/inott.log
; 天
MaxAge              = 7
; MB
MaxSize             = 100
; 个数
MaxBackups          = 100
*/

const (
	LOG_DEFAULT_MAX_AGE    = 7   // 最多保留 7 天
	LOG_DEFAULT_MAX_SIZE   = 100 //最多保留 100MB
	LOG_DEFAULT_MAXBACKUPS = 100 // 最多保留100个文件，100MB*100个=10GB
)

var Logger = zap.L()

func encodeTimeLayout(t time.Time, layout string, enc zapcore.PrimitiveArrayEncoder) {
	type appendTimeEncoder interface {
		AppendTimeLayout(time.Time, string)
	}

	if enc, ok := enc.(appendTimeEncoder); ok {
		enc.AppendTimeLayout(t, layout)
		return
	}

	enc.AppendString(t.Format(layout))
}

// ----------------------------------
//
//	时间格式变更成可读性格式
//
// ----------------------------------
func normalEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	encodeTimeLayout(t, "2006/01/02 15:04:05.000", enc)
}

type MyZapcoreLevel struct {
	zapcore.Level
}

// CapitalString returns an all-caps ASCII representation of the log level.
func (l MyZapcoreLevel) CapitalString() string {
	// Printing levels in all-caps is common enough that we should export this
	// functionality.
	switch l.Level {
	case zapcore.DebugLevel:
		return "D"
	case zapcore.InfoLevel:
		return "I"
	case zapcore.WarnLevel:
		return "W"
	case zapcore.ErrorLevel:
		return "E"
	case zapcore.DPanicLevel:
		return "DP"
	case zapcore.PanicLevel:
		return "P"
	case zapcore.FatalLevel:
		return "F"
	default:
		return fmt.Sprintf("LEVEL(%d)", l)
	}
}

// CapitalLevelEncoder serializes a Level to an all-caps string. For example,
// InfoLevel is serialized to "INFO".
func selfNormalLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	ml := MyZapcoreLevel{l}
	enc.AppendString(ml.CapitalString())
}

func newLogger(dir string, maxSize int, maxAge int, maxBackups int, level zapcore.Level) *zap.Logger {
	logSync := zapcore.AddSync(os.Stdout)

	if len(dir) != 0 {
		logSync = zapcore.AddSync(&lumberjack.Logger{
			Filename:   dir,
			MaxSize:    maxSize,    //megabytes
			MaxAge:     maxAge,     //days
			MaxBackups: maxBackups, //files
		})
	}

	conf := zap.NewProductionEncoderConfig()
	conf.CallerKey = "c"
	conf.LevelKey = "l"
	conf.TimeKey = "t"
	conf.EncodeTime = normalEncoder
	conf.EncodeLevel = selfNormalLevelEncoder
	enc := zapcore.NewJSONEncoder(conf)
	return zap.New(zapcore.NewCore(enc, logSync, level), zap.AddCaller(), zap.AddCallerSkip(1))
}

// ----------------------------------------
//
//	日志初始化
//
// ----------------------------------------
func InitLog(logConf log.LogConfig) error {
	var logKvMap = map[string]int{
		"emergency": 5,
		"alert":     4,
		"critical":  3,
		"error":     2,
		"warning":   1,
		"info":      0,
		"debug":     -1,
	}

	level := 0

	// 如果配置文件显示配置了level，直接取配置文件的
	confLevel, ok := logKvMap[strings.ToLower(logConf.Level)]
	if ok {
		level = confLevel
	}

	if logConf.MaxAge == 0 {
		logConf.MaxAge = LOG_DEFAULT_MAX_AGE
	}

	if logConf.MaxSize == 0 {
		logConf.MaxSize = LOG_DEFAULT_MAX_SIZE
	}

	if logConf.MaxBackUps == 0 {
		logConf.MaxBackUps = LOG_DEFAULT_MAXBACKUPS
	}

	newLoggerObj := newLogger(logConf.Path, logConf.MaxSize, logConf.MaxAge, logConf.MaxBackUps, zapcore.Level(level))
	Logger = newLoggerObj

	// 全局文件中的 logger 进行替代
	zap.ReplaceGlobals(newLoggerObj)

	return nil
}

func Debug(format string, args ...interface{}) {
	zap.S().Debugf(format, args...)
}

func Info(format string, args ...interface{}) {
	zap.S().Infof(format, args...)
}

func Warn(format string, args ...interface{}) {
	zap.S().Warnf(format, args...)
}

func Error(format string, args ...interface{}) {
	zap.S().Errorf(format, args...)
}

func doHeadTraceId(ctx context.Context) string {
	return GetTraceIdByCtx(ctx)
}

func DebugWithCtx(ctx context.Context, format string, args ...interface{}) {
	traceId := doHeadTraceId(ctx)
	zap.S().With("trace_id", traceId).Debugf(format, args...)
}

func InfoWithCtx(ctx context.Context, format string, args ...interface{}) {
	traceId := doHeadTraceId(ctx)
	zap.S().With("trace_id", traceId).Infof(format, args...)
}

func WarnWithCtx(ctx context.Context, format string, args ...interface{}) {
	traceId := doHeadTraceId(ctx)
	zap.S().With("trace_id", traceId).Warnf(format, args...)
}

func ErrorWithCtx(ctx context.Context, format string, args ...interface{}) {
	traceId := doHeadTraceId(ctx)
	zap.S().With("trace_id", traceId).Errorf(format, args...)
}

func DebugRaw(format string, args ...interface{}) {
	zap.S().Debugf(format, args...)
}

func DoGetJsonStrWithLogID(logID string, format string, args ...interface{}) string {
	msg := fmt.Sprintf(format, args...)
	type Rt struct {
		LogID string `json:"log_id"`
		Msg   string `json:"msg"`
	}
	rt := Rt{
		LogID: logID,
		Msg:   msg,
	}

	bf := bytes.NewBuffer([]byte{})
	jsonEncoder := json.NewEncoder(bf)
	jsonEncoder.SetEscapeHTML(false)
	err := jsonEncoder.Encode(rt)
	if err != nil {
		fmt.Println("json marsha error. ", err)
		return ""
	}

	rtStr := bf.String()
	if len(rtStr) == 0 {
		return rtStr
	}
	return rtStr[:len(rtStr)-1]
}

func Flush() {
	//Logger.Flush()
}

// ---------------------------------------------
//
//	从 ctx 中提取 traceId
//	如果有自定义的 traceId，那么直接返回
//	如果没有自定义 traceId，那么返回空字符串
//
// ---------------------------------------------
func GetTraceIdByCtx(ctx context.Context) string {
	if ctx == nil {
		return ""
	}

	traceId, ok := ctx.Value(ContextTraceIDKey).(string)
	if !ok {
		return ""
	}
	return traceId
}
